package io.sundial.util;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 类型工具类
 *
 * @author Payne 646742615@qq.com
 * 2018/12/17 14:10
 */
public abstract class TypKit {

    public static Type derive(final Class<?> root, final Class<?> type, final int index) {
        TypeVariable<? extends Class<?>>[] typeParameters = type.getTypeParameters();
        if (index < 0 || typeParameters.length <= index) {
            throw new IndexOutOfBoundsException("type parameters index range of " + type + " is [0, " + typeParameters.length + ") which do not contains " + index);
        }
        return derive(root, type, index, Collections.<String, Type>emptyMap());
    }

    private static Type derive(final Class<?> root, final Class<?> type, final int index, final Map<String, Type> typeArguments) {
        Type[] interfaces = root.getGenericInterfaces();
        // 先从接口开始寻找
        for (Type interfase : interfaces) {
            // 父类是一个泛型类
            if (interfase instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) interfase;
                Type derived = derive(parameterizedType, type, index, typeArguments);
                if (derived != null) {
                    return derived;
                }
            }
            // 父类是一个普通类，之前的推导类型参数映射在这里断了！
            if (interfase instanceof Class<?>) {
                Class<?> clazz = (Class<?>) interfase;
                Type derived = derive(clazz, type, index, Collections.<String, Type>emptyMap());
                if (derived != null) {
                    return derived;
                }
            }
        }
        // 再从父类开始寻找
        Type superclass = root.getGenericSuperclass();
        // 父类是一个泛型类
        if (superclass instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) superclass;
            return derive(parameterizedType, type, index, typeArguments);
        }
        // 父类是一个普通类，之前的推导类型参数映射在这里断了！
        if (superclass instanceof Class<?>) {
            Class<?> clazz = (Class<?>) superclass;
            return derive(clazz, type, index, Collections.<String, Type>emptyMap());
        }
        // 如果没有匹配的则返回null
        return null;
    }

    private static Type derive(final ParameterizedType parameterizedType, final Class<?> type, final int index, final Map<String, Type> typeArguments) {
        Class<?> rawType = (Class<?>) parameterizedType.getRawType();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        // 找到了
        if (rawType == type) {
            Type actualTypeArgument = actualTypeArguments[index];
            if (actualTypeArgument instanceof TypeVariable<?>) {
                TypeVariable<?> typeVariable = (TypeVariable<?>) actualTypeArgument;
                String name = typeVariable.getName();
                actualTypeArgument = typeArguments.containsKey(name) ? typeArguments.get(name) : actualTypeArgument;
            }
            return actualTypeArgument;
        }
        // 继续推导，而且传递祖先类中被推倒出的类型参数实际类型。
        else {
            TypeVariable<? extends Class<?>>[] typeParameters = rawType.getTypeParameters();
            Map<String, Type> derivedArguments = new HashMap<>();
            for (int i = 0; i < typeParameters.length && i < actualTypeArguments.length; i++) {
                TypeVariable<? extends Class<?>> typeParameter = typeParameters[i];
                Type actualTypeArgument = actualTypeArguments[i];
                if (actualTypeArgument instanceof TypeVariable<?>) {
                    TypeVariable<?> typeVariable = (TypeVariable<?>) actualTypeArgument;
                    String name = typeVariable.getName();
                    actualTypeArgument = typeArguments.containsKey(name) ? typeArguments.get(name) : actualTypeArgument;
                }
                String name = typeParameter.getName();
                derivedArguments.put(name, actualTypeArgument);
            }
            return derive(rawType, type, index, derivedArguments);
        }
    }

}
